package org.bookie.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Transient;

import org.hibernate.annotations.Parent;

/**
 * Class modelling an account for an {@link User}. An account belongs to a
 * single {@link User} and contains all the {@link Transaction}s of the
 * account.
 * 
 * @author Igor Drobiazko
 */
@Embeddable
public class Account implements Serializable {
	private static final int REFILL_THRESHOLD = 10;
	private static final long serialVersionUID = 1L;
	public static final Double REFILL_AMOUNT = new Double(1000);
	public static final long REFILL_LOCK_PERIOD = 1000 * 60 * 60 * 24;
	/** Owner of the account */
	@Parent
	private User owner;

	/** Balance of the account */
	@Transient
	private Double balance = new Double(0);
	
	/** Balance of all the bet slips */
	@Transient
	private Double betSlipBalance = new Double(0);

	/** The date of the last refill */
	@Column
	private Date refilled;

	/** List of transactions of the account */
	@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
	private List<Transaction> transactions = new ArrayList<Transaction>();

	protected Account() {
		this(new Double(0));
	}

	public Account(Double balance) {
		this.balance = balance;
	}

	/**
	 * Deposit specified amount to the account.
	 * 
	 * @param amount amount to deposit
	 * @param comment key to get a comment from a resource bundle
	 */
	public void deposit(Double amount, Comment comment) {
		getTransactions().add(
				new Transaction(getOwner(), amount, comment, null));
	}

	/**
	 * Deposit the profit of the specified {@link BetSlip} to the account.
	 * 
	 * @param betSlip bet slip to deposit
	 */
	public void deposit(BetSlip betSlip) {
		validateBetSlip(betSlip);
		Double amount = new Double(betSlip.getProfit().doubleValue());
		getTransactions().add(
				new Transaction(betSlip.getUser(), amount,
						Comment.PROFIT, betSlip));
	}

	/**
	 * Withdraw specified amount from the account.
	 * 
	 * @param amount amount to withdraw
	 * @param comment key to get a comment from a resource bundle
	 */
	public void withdraw(Double amount, Comment comment) {
		amount = new Double((-1) * amount.doubleValue());
		getTransactions().add(
				new Transaction(getOwner(), amount, comment, null));
	}

	/**
	 * Withdraw the total stake of the specified {@link BetSlip} from the
	 * account.
	 * 
	 * @param amount bet slip to withdraw total stake from
	 */
	public void withdraw(BetSlip betSlip) {
		validateBetSlip(betSlip);
		Double amount = new Double((-1) * betSlip.getTotalStake().doubleValue());
		Transaction trx = new Transaction(betSlip.getUser(), amount,
				Comment.STAKE, betSlip);
		getTransactions().add(trx);
	}

	/** Refills the play money. */
	public boolean refill() {
		if (!isRefillable())
			return false;
		refilled = new Date();
		deposit(Account.REFILL_AMOUNT, Comment.REFILL);
		setBalance(new Double(getBalance().doubleValue()
				+ Account.REFILL_AMOUNT.doubleValue()));
		return true;
	}

	/**
	 * Returns <code>true</code> if user is allowed to refill its account.
	 */
	public boolean isRefillable() {
		if (REFILL_THRESHOLD < getBalance().doubleValue())
			return false;
		if (getRefilled() != null) {
			long time = getRefilled().getTime() + Account.REFILL_LOCK_PERIOD;
			if (System.currentTimeMillis() < time)
				return false;
		}
		return true;
	}

	private void validateBetSlip(BetSlip betSlip) {
		if (!getOwner().getId().equals(betSlip.getUser().getId())) {
			throw new RuntimeException(
					"It is not allowed to withdraw/deposit a stake of a bet slip not belonging"
							+ " to the account owner");
		}
	}

	/**
	 * Get the sum of all transactions.
	 * 
	 * @return instance of Double
	 */
	public Double getBalance() {
		return balance;
	}

	/**
	 * Set the sum of all transactions.
	 * 
	 * @param balance
	 *            sum to set
	 */
	public void setBalance(Double balance) {
		this.balance = balance;
	}

	/**
	 * Get the owner of the account
	 * 
	 * @return instance of User
	 */
	public User getOwner() {
		return owner;
	}

	/**
	 * Set the owner of the account
	 * 
	 * @param owner
	 *            user to set
	 */
	public void setOwner(User owner) {
		this.owner = owner;
	}

	/**
	 * Get all transactions of the account
	 * 
	 * @return instance of List
	 */
	public List<Transaction> getTransactions() {
		return transactions;
	}

	/**
	 * Returns the date of the last refill
	 */
	public Date getRefilled() {
		return refilled;
	}

	public Double getBetSlipBalance() {
		return betSlipBalance;
	}

	public void setBetSlipBalance(Double betSlipBalance) {
		this.betSlipBalance = betSlipBalance;
	}
}
